// Copyright 2013 The Chromium Authors. All rights reserved.
// Use of this source code is governed by a BSD-style license that can be
// found in the LICENSE file.

#include "gin/modules/timer.h"

#include "base/memory/scoped_ptr.h"
#include "base/message_loop/message_loop.h"
#include "gin/handle.h"
#include "gin/object_template_builder.h"
#include "gin/public/isolate_holder.h"
#include "gin/shell_runner.h"
#include "gin/test/v8_test.h"
#include "gin/try_catch.h"
#include "gin/wrappable.h"
#include "v8/include/v8.h"

namespace gin {

namespace {

    class Result : public Wrappable<Result> {
    public:
        static WrapperInfo kWrapperInfo;
        static Handle<Result> Create(v8::Isolate* isolate)
        {
            return CreateHandle(isolate, new Result());
        }

        int count() const { return count_; }
        void set_count(int count) { count_ = count; }

        void Quit()
        {
            base::MessageLoop::current()->QuitNow();
        }

    private:
        Result()
            : count_(0)
        {
        }

        ~Result() override { }

        ObjectTemplateBuilder GetObjectTemplateBuilder(
            v8::Isolate* isolate) override
        {
            return Wrappable<Result>::GetObjectTemplateBuilder(isolate)
                .SetProperty("count", &Result::count, &Result::set_count)
                .SetMethod("quit", &Result::Quit);
        }

        int count_;
    };

    WrapperInfo Result::kWrapperInfo = { gin::kEmbedderNativeGin };

    struct TestHelper {
        TestHelper(v8::Isolate* isolate)
            : runner(new ShellRunner(&delegate, isolate))
            , scope(runner.get())
            , timer_module(TimerModule::Create(isolate))
            , result(Result::Create(isolate))
        {
            EXPECT_FALSE(runner->global().IsEmpty());
            runner->global()->Set(StringToV8(isolate, "timer"),
                timer_module->GetWrapper(isolate));
            runner->global()->Set(StringToV8(isolate, "result"),
                result->GetWrapper(isolate));
        }

        void QuitSoon(base::MessageLoop* message_loop)
        {
            message_loop->PostDelayedTask(FROM_HERE,
                base::MessageLoop::QuitWhenIdleClosure(),
                base::TimeDelta::FromMilliseconds(0));
        }

        ShellRunnerDelegate delegate;
        scoped_ptr<ShellRunner> runner;
        Runner::Scope scope;
        Handle<TimerModule> timer_module;
        Handle<Result> result;
    };

} // namespace

typedef V8Test TimerUnittest;

TEST_F(TimerUnittest, OneShot)
{
    TestHelper helper(instance_->isolate());
    std::string source = "timer.createOneShot(0, function() {"
                         "  result.count++;"
                         "});";

    helper.runner->Run(source, "script");
    EXPECT_EQ(0, helper.result->count());

    helper.QuitSoon(&message_loop_);
    message_loop_.Run();
    EXPECT_EQ(1, helper.result->count());
}

TEST_F(TimerUnittest, OneShotCancel)
{
    TestHelper helper(instance_->isolate());
    std::string source = "var t = timer.createOneShot(0, function() {"
                         "  result.count++;"
                         "});"
                         "t.cancel()";

    helper.runner->Run(source, "script");
    EXPECT_EQ(0, helper.result->count());

    helper.QuitSoon(&message_loop_);
    message_loop_.Run();
    EXPECT_EQ(0, helper.result->count());
}

TEST_F(TimerUnittest, Repeating)
{
    TestHelper helper(instance_->isolate());

    // TODO(aa): Cannot do: if (++result.count == 3) because of v8 bug. Create
    // test case and report.
    std::string source = "timer.createRepeating(0, function() {"
                         "  result.count++;"
                         "  if (result.count == 3) {"
                         "    result.quit();"
                         "  }"
                         "});";

    helper.runner->Run(source, "script");
    EXPECT_EQ(0, helper.result->count());

    message_loop_.Run();
    EXPECT_EQ(3, helper.result->count());
}

TEST_F(TimerUnittest, TimerCallbackToDestroyedRunner)
{
    TestHelper helper(instance_->isolate());
    std::string source = "timer.createOneShot(0, function() {"
                         "  result.count++;"
                         "});";

    helper.runner->Run(source, "script");
    EXPECT_EQ(0, helper.result->count());

    // Destroy runner, which should destroy the timer object we created.
    helper.QuitSoon(&message_loop_);
    helper.runner.reset(NULL);
    message_loop_.Run();

    // Timer should not have run because it was deleted.
    EXPECT_EQ(0, helper.result->count());
}

} // namespace gin
